package com.zmn.mcc.cas.server.shiro;

import com.alibaba.fastjson.JSONObject;
import com.zmn.common.constant.StatusConsts;
import com.zmn.common.dto2.ResponseDTO;
import com.zmn.common.utils.string.StringUtil;
import com.zmn.mcc.cas.CasConstants;
import com.zmn.mcc.cas.core.AuthTokenDO;
import com.zmn.mcc.cas.core.ShiroRealm;
import com.zmn.mcc.cas.core.ShiroTokenManager;
import com.zmn.mcc.cas.model.LoginStaff;
import com.zmn.mcc.cas.model.UserTokenDO;
import com.zmn.mcc.cas.server.CasServerConsts;
import com.zmn.mcc.cas.server.CasServerStaffService;
import com.zmn.mcc.cas.web.StaffAdminService;
import com.zmn.mcc.common.dto.permit.PermitLitDO;
import com.zmn.mcc.cache.DeptCache;
import com.zmn.mcc.cache.RoleCache;
import com.zmn.mcc.cache.StaffCache;
import com.zmn.mcc.manager.config.MccConfig;
import com.zmn.mcc.model.entity.staff.McStaff;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.permission.AllPermission;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.*;

/**
 * @author nowind
 * @since 2017/9/11 17:50
 */
public class StaffShiroRealm extends ShiroRealm {

    private Logger logger = LoggerFactory.getLogger(StaffShiroRealm.class);

    private ShiroTokenManager shiroTokenManager;

//    @Resource
//    protected PermitCache permitCache;

    private DeptCache deptCache;

    private RoleCache roleCache;

    private MccConfig config;

    private StaffCache staffCache;

    private CasServerStaffService casServerStaffService;


    public StaffShiroRealm(ShiroTokenManager shiroTokenManager, CasServerStaffService casServerStaffService, DeptCache deptCache, RoleCache roleCache, MccConfig config, StaffCache staffCache) {
        this.shiroTokenManager = shiroTokenManager;
        this.casServerStaffService = casServerStaffService;
        this.deptCache = deptCache;
        this.roleCache = roleCache;
        this.config = config;
        this.staffCache = staffCache;
    }

    /**
     * 获取身份验证信息
     *
     * @param authcToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {

        AuthTokenDO authToken = (AuthTokenDO) authcToken;
        String staffName = authToken.getUsername().trim();
        String password = authToken.getPwd();
        String dTalkUserId = authToken.getdTalkUserId();
        ResponseDTO<McStaff> responseDTO;
        McStaff staff = null;
        try {
            // 用户名或密码为空
            if (!authToken.isQrCodeLogin() && (StringUtil.isBlank(staffName) || StringUtil.isBlank(password))) {
                throw new UnknownAccountException("用户或密码不能为空");
            }

            // 验证码
            // 登录失败次数过多 已移动
//            int loginFailTimes = permitCache.getLoginFailTimes(staffName);
//            if (loginFailTimes > PermitConsts.LOGIN_FAIL_LIMIT) {
//                throw new ExcessiveAttemptsException(String.format("密码错误已超过%s次,请%s分钟后再试", PermitConsts.LOGIN_FAIL_LIMIT, PermitConsts.LOGIN_LOCK_TIMESPAN / 60));
//            }

            // 验证用户并返回
            if (authToken.isQrCodeLogin()) {
                responseDTO = casServerStaffService.checkDTalkUserId(staffName, dTalkUserId);
            } else {
                responseDTO = casServerStaffService.checkPassword(staffName, password.trim());
            }


            // 登录失败
            if (responseDTO.getStatus() != StatusConsts.STATUS_SUCCESS) {
                switch (responseDTO.getStatus()) {
                    case StatusConsts.ERROR_NOTUSER:
                        throw new UnknownAccountException("用户不存在");
                    case StatusConsts.FAIL_LOGIN_DISABLE:
                        throw new DisabledAccountException("用户已经被禁用或锁定");
                    case StatusConsts.FAIL_LOGIN_PASSWORD:
                        throw new IncorrectCredentialsException("用户名/密码错误");
                    default:
                        throw new AuthenticationException("认证失败");
                }
            } else {
//                staff = responseDTO.getData();
                // TODO 未知用处
                // 登录成功,处理登录后事务
                // 清除密码错误计数器
//                permitCache.removeLoginFailTimes(staffName);
                // 缓存用户信息到redis
//                permitCache.setStaffToken(staff);

            }

            staff = responseDTO.getData();

            LoginStaff loginStaff = casServerStaffService.getLoginStaffDOByMcStaff(staff);

            UserTokenDO userToken = new UserTokenDO();
            userToken.setUserKey(String.valueOf(loginStaff.getStaffId()));
            userToken.setUserObj(JSONObject.toJSONString(loginStaff));
            userToken.setStatusCode(responseDTO.getStatus());

            // 将staffToken写入session
            if (authToken.isQrCodeLogin()) {
                //没有明文密码，需要设置authToken的password值，让shiro通过校验
                authToken.setPassword(staff.getPassword().toCharArray());
                return new SimpleAuthenticationInfo(userToken, staff.getPassword(), getName());
            } else {
                return new SimpleAuthenticationInfo(userToken, authToken.getPassword(), getName());
            }

        } catch (Exception ex) {
            if (ex instanceof UnknownAccountException || ex instanceof ExcessiveAttemptsException ||
                    ex instanceof DisabledAccountException || ex instanceof IncorrectCredentialsException) {
                throw new AuthenticationException(ex.getMessage());
            } else {
                logger.error(ex.getMessage(), ex);
                throw new AuthenticationException("登录异常,请稍后重试！");
            }
        }


    }

    /**
     * 根据用户身份获取授权信息
     * 当用户调用Security.getSubject().isPermitted(permissions)，当用户调用Security.getSubject().hasRole(roleIdentifier)
     *
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//        String tokenKey = shiroTokenManager.getUserTokenKey();
//        Integer staffId = StringUtil.isBlank(tokenKey) ? 0 : Integer.parseInt(tokenKey);

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

        // 查询用户拥有的角色集合
//        List<Integer> roleIds = casServerStaffService.listAvailableRoleIdsByStaffId(staffId);

        LoginStaff staff = StaffAdminService.currentStaff();

        logger.debug("#cas#authorization staffId:{}, staffName:{}, roleIds:{}", staff.getStaffId(), staff.getStaffName(), staff.getRoleIds());

//        Set<String> roleIdSet = new HashSet<>();

//        for (Integer roleId : roleIds) {
//            roleIdSet.add(String.valueOf(roleId));
//        }
        if (staff.getRoleIds() != null) {
            Set<String> set = new HashSet<>();
            for (Integer role : staff.getRoleIds()) {
                set.add(role.toString());
            }
            authorizationInfo.setRoles(set);
        }

        // 查询用户拥有的权限集合
        List<PermitLitDO> permits = casServerStaffService.listPermitOfCurrentAppByStaffId(staff);
        Set<String> permitUrlSet = new HashSet<>();
        if (permits != null) {
            for (PermitLitDO permit : permits) {
                if (StringUtils.isNotEmpty(permit.getUrl())) {
                    permitUrlSet.add(permit.getUrl().trim());
                }
            }
        }
        authorizationInfo.addStringPermissions(permitUrlSet);
        // 超级管理员
        if(authorizationInfo.getRoles() != null && authorizationInfo.getRoles().contains(CasConstants.SUPER_ADMIN_ROLE_ID)){
            authorizationInfo.addObjectPermission(new AllPermission());
        }
        logger.debug("#cas#authorization staffId:{}, staffName:{}, permitUrls:{}", staff.getStaffId(), staff.getStaffName(), permitUrlSet);
        return authorizationInfo;
    }

    @Override
    protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
        Serializable sessionId = null;
        UserTokenDO userTokenDO = (UserTokenDO) principals.getPrimaryPrincipal();
        if (userTokenDO != null && StringUtils.isNotEmpty(userTokenDO.getSessionId())) {
            sessionId = userTokenDO.getSessionId();
        } else {
            Session session = shiroTokenManager.getSession();
            if (session != null) {
                sessionId = session.getId();
            }
        }

        return CasServerConsts.MC_AUTHORIZATION_STAFF + sessionId;
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        return token != null && (token instanceof AuthTokenDO || token instanceof UsernamePasswordToken);
    }
}
